<?php

namespace app\models\cacheModel;

use app\entity\PageListEntity;
use app\models\cacheModel\base\NewsCategory;
use app\models\cacheModel\SettingModel as SettingCacheModel;
use app\models\tableModel\NewsCategoryModel as NewsCategoryDbModel;
use app\service\tool\ConvertService;
use app\service\tool\UtilsService;
use yii\db\Expression;
use yii\db\ExpressionInterface;
use yii\helpers\ArrayHelper;

/**
 * [文章分类]缓存操作模型 缓存[Model]
 * 作者: Editor Name
 * 日期: 2022/03/23
 * 时间: 09:23:01
 */
class NewsCategoryModel extends NewsCategory
{

    /**
     * 数据库实例
     * @var NewsCategoryDbModel
     */
    private $dbInstance;

    public function __construct(array $config = [])
    {

        parent::__construct($config);
        // 先初始化下类
        $this->setDbInstance();
    }

    /**
     * 规则验证
     * @return array
     */
    public function rules()
    {

        // 走数据库的规则
        $parent = parent::rules();

        return ArrayHelper::merge($parent, []);
    }

    /**
     * 字段属性
     * @return array
     */
    public function attributes()
    {

        return ArrayHelper::merge(parent::attributes(), [

        ]);
    }

    /**
     * 字段文本
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return ArrayHelper::merge(parent::attributeLabels(), []);
    }

    /**
     * 场景
     * @return array
     */
    public function scenarios()
    {

        $scenarios = parent::scenarios();
        return ArrayHelper::merge($scenarios, [
            // 自定义场景 (无用请删除)
            'scUpdate' => [
                'someAttributes'
            ]
        ]);
    }

    /**
     * 初始化[数据库]实例
     *  ` 逻辑：先从缓存中获取条目，条目不存在，从数据库拿，并存入缓存，返回[self] | 可以强制必须获取数据库`
     * @param string $id 数据编号|直接初始化
     * @param string $scenario 场景|ps.缓存类场景
     * @return NewsCategoryModel
     */
    public static function loadModelDB($id = null, $scenario = 'default')
    {

        ### 强制数据库 || 缓存未找到
        $dbModel = NewsCategoryDbModel::loadModel($id);
        // 数据库查询的空 - 返回数据空
        if (!$dbModel) return null;

        ### 查询缓存
        // 查询缓存是否存在条目
        $model = self::loadModel($dbModel['id'], $scenario);
        // 缓存为空 初始化缓存 && 保存
        if (!$model) {
            $model     = self::loadModel(true, $scenario);
            $attribute = $dbModel->getAttributes();
            $model->setAttributes($attribute);
            $model->save(false, null, false); // 保存一下
            $model->setIsNewRecord(false); // 同步之后就不是新建了
        }

        // 赋值数据库实例
        $model->setDbInstance($dbModel);

        return $model;
    }

    /**
     * 赋值数据库实例
     * @param NewsCategoryDbModel|bool $dbModel
     * @return NewsCategoryModel
     */
    public function setDbInstance($dbModel = false)
    {

        if (!$dbModel) {
            $this->dbInstance = NewsCategoryDbModel::loadModel();
        } else {
            $this->dbInstance = $dbModel;
        }
        return $this;
    }

    /**
     * 获取数据库实例
     * @return NewsCategoryDbModel
     */
    public function getDbInstance()
    {
        return $this->dbInstance;
    }

    /**
     * 自定义过滤单条[where]
     *  ` 仅限于通过方法[loadWhere]传入的条件, 可以在其中自定义逻辑
     *  ` 保证最终返回值为一维数组[Yii2]通用条件格式[运算符, 字段名, 值]
     * @param $condition array [sql]查询条件, 严格遵循Yii多条件查询:[运算符, 字段名, 值]
     * @return array
     */
    public function whereCustomFilter($condition = [])
    {

        // // 例：字段为编号 —— 操作符使用[IN]
        // if ($field == 'id') {
        //     return ['IN', $condition[1], $value[2]];
        // }

        // 最终返回必须为数组：[运算符, 字段名, 值]，符合Yii2规范
        return $condition;
    }

    /**
     * 获取分页信息
     * @param int $page 当前页
     * @param int $limit 展示多少条
     * @param array $opt 其他设置
     *  - loadChild - bool 加载子集
     *  - parentId - array 加载某条目列表
     *  - childNoDel - bool 子集没有是否删除子集键
     * @return PageListEntity
     */
    public function getPaginate($page, $limit, $opt = [])
    {

        // 当前页面计算
        $page = max($page - 1, 0);

        ### 某些前置设置
        // 过滤编号
        if (isset($opt['removeIds']) && $opt['removeIds']) {
            $this->getBaseSql()->andWhere(['AND', ['NOT IN', 'id', $opt['removeIds']]]);
        }
        // 需要查询父级编号的
        $loadChild = isset($opt['loadChild']) && $opt['loadChild'];
        if ($loadChild && isset($opt['parentId'])) {
            $this->getBaseSql()->where(
                UtilsService::whereRm($this->where, 'parent_id')
            )->andWhere([
                'AND',
                ['IN', 'parent_id', $opt['parentId']]
            ]);
        }

        // 数据的获取 分页等
        $list = $this->getBaseSql()->offset($page * $limit)
            ->limit($limit)
            ->asArray()->all();

        // 格式化数据后的列表
        $list = $this->formatData($list, $opt);
        // 总数量
        $total = $this->getBaseSql()->count();

        return PageListEntity::loadModel(compact('list', 'total'));
    }

    /**
     * 格式化列表活详情数据
     * @param array $list 列表
     * @param array $opt 其他设置
     * @return mixed
     */
    public static function formatData($list, $opt = [])
    {

        // 为空直接返回
        if (empty($list)) return $list;
        // 需要返回第一组（可能不是二维数组）
        $needFirst = false;
        if (!is_array(array_values($list)[0])) {
            $needFirst = true;
            $list      = [$list];
        }

        ### 某些参数初始化
        // 加载子集列表
        $opt['loadChild'] = !(!isset($opt['loadChild']) || !$opt['loadChild']);
        // 是否删除无子集的夏季列表键
        $opt['childNoDel'] = !(!isset($opt['childNoDel']) || !$opt['childNoDel']);
        // 需要排除编号的
        $removeId = [];
        if (isset($opt['removeIds']) && $opt['removeIds']) $removeId = $opt['removeIds'];

        ### 开始格式化
        foreach ($list as $k => &$v) {

            // 更新时间
            if (!empty($v['update_time'])) {
                $v['update_time_text']   = date('Y-m-d H:i:s', $v['update_time']);
                $v['update_time_text_s'] = date('Y-m-d', $v['update_time']);
            }

            // 状态文本
            if (isset($v['status'])) {
                $v['status_text'] = NewsCategoryDbModel::getStatusText($v['status']);
            }

            // 需要加载是否有子集
            if ($opt['loadChild']) {

                ### 是否存在子集
                $v['had_children'] = false;
                // 没有子集了 返回
                $childCount        = self::find()->where([
                    'parent_id' => $v['id'],
                    'status'    => $v['status'],
                    'is_show'   => !isset($v['is_show']) ? [
                        NewsCategoryDbModel::getIsShowShow(),
                        NewsCategoryDbModel::getIsShowHide()
                    ] : $v['is_show']
                ])->andWhere([
                    'AND',
                    ['NOT IN', 'id', $removeId]
                ])->count();
                $v['had_children'] = false;
                if ($childCount >= 1) $v['had_children'] = true;

                ### 子集列表
                $v['children'] = self::loadModel()->getPaginate(1, 200, array_merge($opt, [
                    'parentId' => $v['id']
                ]))->getList();
                // 子集编号列表
                $v['child_id_list'] = array_column($v['children'], 'id');
                // 一级列表 - 涉及2个级别列表
                if (!empty($v['level']) && intval($v['level']) == 1) {
                    $v['lv_id_list'] = UtilsService::arrayListMerge(array_column(
                            $v['children'], 'child_id_list')
                    );
                }
                if ($opt['childNoDel'] && empty($v['children'])) unset($v['children']);
            }

            // 连接 - 站内连接需格式化(默认分类的连接，循环内直接调用[$data.url]即可)
            if ($v['url_type'] == NewsCategoryDbModel::getUrlTypeSite() && !empty($opt['baseUrl'])) {
                $v['url'] = str_replace(['{id}'], [$v['id']], $opt['baseUrl']);
            }

            // 附加数据
            if (!empty($v['extra_data'])) {
                // 如家数据格式化
                $v['extra_data'] = NewsCategoryDbModel::formatExtraData($v['extra_data']);
            }
        }

        reset($list);
        return $needFirst ? current($list) : $list;
    }

    /**
     * 同步|新建数据库数据
     * @return bool
     */
    public function saveDbData() {

        // 主键
        $pk = $this->getPrimaryKey();
        // 需要新建 - 初始化
        $db = NewsCategoryDbModel::loadModel(!empty($pk) ? $pk : true);
        // 保存
        $db->setAttributes($this->getAttributes());
        if (!$db->save()) {$this->addErrors($db->getErrors());return false;}
        // 数据库数据原封不动赋值下 - 以数据库为主
        $this->setAttributes($db->getAttributes());

        return true;
    }

    /**
     * 添加|保存
     * @param bool $runValidation 是否仅仅验证不保存
     * @param string[]|null|string $attributeNames 仅仅保存某几个字段
     * @param bool $saveDb 是否保存数据库
     * @return bool
     */
    public function save($runValidation = false, $attributeNames = null, $saveDb = true)
    {

        $dbTran = \Yii::$app->db->beginTransaction();

        try {

            ### 先保存数据库
            // 查询数据库记录
            if (!$runValidation && $saveDb  === true && !$this->saveDbData()) {
                $dbTran->rollBack();
                return false;
            }

            ### 批量操作[缓存保存前一些格式化]
            foreach ($this->getAttributes() as $k => $v) {
                // 字段类型为[JSON]类型需要转为数组 - 保存自动转为[JSON]
                if (is_array($v)) {
                    $this->setAttribute($k, json_encode($v, JSON_UNESCAPED_UNICODE));
                }

                // 空的 - 赋值空的数据 保证字段在其中
                if ($v === null) $this->setAttribute($k, '');
            }

            ### 保存此条缓存记录
            if ($this->hasErrors() || !$this->validate($attributeNames)) {

                $dbTran->rollBack();
                // 记录下错误日志
                \Yii::error([

                    "`````````````````````````````````````````````````````````",
                    "``                      缓存保存错误                      ``",
                    "`` 错误详情: [[文章分类]缓存操作]验证数据失败                ``",
                    "`` 错误信息和参数详情:                                     ``",
                    "`````````````````````````````````````````````````````````",
                    $this->getErrors()
                ], 'cache');
                return false;
            }

            ### 保存此条缓存记录
            if (!$runValidation && !parent::save(false, $attributeNames)) {

                $dbTran->rollBack();
                // 记录下错误日志
                \Yii::error([
                    "`````````````````````````````````````````````````````````",
                    "``                      缓存保存错误                      ``",
                    "`` 错误详情: [[文章分类]缓存操作]保存数据失败                ``",
                    "`` 错误信息和参数详情:                                     ``",
                    "`````````````````````````````````````````````````````````",
                    $this->getErrors()
                ], 'cache');
                return false;
            }

            $dbTran->commit();
            return true;

        } catch (\Exception $error) {

            $dbTran->rollBack();
            // 记录下错误日志
            \Yii::error([

                "`````````````````````````````````````````````````````````",
                "``                      缓存保存错误                      ``",
                "`` 错误详情: [[管理员角色]缓存操作]出现错误                   ``",
                "`` 错误信息和参数详情:                                     ``",
                "`````````````````````````````````````````````````````````",
                $this->getErrors()
            ], 'cache');
            return false;
        }
    }

    /**
     * 获取模板具体地址
     * @param bool $getNewsTemp
     * @return string
     */
    public function getTemplate($getNewsTemp = false)
    {

        $device = UtilsService::isMobile() ? 'mob' : 'pc';
        ### 如果直接获取文章页面模板
        if ($getNewsTemp) {
            return $this->getAttribute($device . '_detail_temp');
        }

        ### 如果是一级
        // 返回分类分类模板
        if ($this->getAttribute('level') == 1) {
            return $this->getAttribute($device . '_cate_temp');
        }

        ### 查询子集
        // 子集存在 返回分类页模板
        $child = self::findOne([
            'parent_id' => $this->getAttribute('id'),
            'status'    => NewsCategoryDbModel::getStatusOpen()
        ]);
        if ($child) {
            return $this->getAttribute($device . '_cate_temp');
        }


        ### 其余都返回列表页
        return $this->getAttribute($device . '_list_temp');
    }

    /**
     * 获取SEO站点 标题
     * @param bool $getNews 获取文章页SEO信息
     * @return array|mixed|string|null
     */
    public function getSeoTitle($getNews = false)
    {
        $setting = $getNews ? $this->getAttribute('news_setting') :
            $this->getAttribute('setting');
        if (ConvertService::isJson($setting)) $setting = json_decode($setting, true);
        $data = empty($setting['seo_title']) ? null : $setting['seo_title'];

        // 如果是有[SEO]信息直接返回
        if (!empty($data)) return $data;

        // 没有返回总设置里的[SEO]信息
        return SettingCacheModel::getSetting('site_title');
    }

    /**
     * 获取SEO站点 关键词
     * @param bool $getNews 获取文章页SEO信息
     * @return array|mixed|string|null
     */
    public function getSeoKeywords($getNews = false)
    {
        $setting = $getNews ? $this->getAttribute('news_setting') :
            $this->getAttribute('setting');
        if (ConvertService::isJson($setting)) $setting = json_decode($setting, true);
        $data = empty($setting['seo_keywords']) ? null : $setting['seo_keywords'];

        // 如果是有[SEO]信息直接返回
        if (!empty($data)) return $data;

        // 没有返回总设置里的[SEO]信息
        return SettingCacheModel::getSetting('seo_keywords');
    }

    /**
     * 获取SEO站点 说明
     * @param bool $getNews 获取文章页SEO信息
     * @return array|mixed|string|null
     */
    public function getSeoDescription($getNews = false)
    {
        $setting = $getNews ? $this->getAttribute('news_setting') :
            $this->getAttribute('setting');
        if (ConvertService::isJson($setting)) $setting = json_decode($setting, true);
        $data = empty($setting['seo_description']) ? null : $setting['seo_description'];

        // 如果是有[SEO]信息直接返回
        if (!empty($data)) return $data;

        // 没有返回总设置里的[SEO]信息
        return SettingCacheModel::getSetting('seo_description');
    }

    /**
     * 分类自身是否开启SEO
     * @return bool
     */
    public function isOpenSeo()
    {
        $setting = $this->getAttribute('setting');
        if (ConvertService::isJson($setting)) $setting = json_decode($setting, true);
        $openValue = NewsCategoryDbModel::getOpenSeoOpen();
        return !empty($setting['open_seo']) && $setting['open_seo'] == $openValue;
    }


    /**
     * 更新某些字段
     * @param $condition
     * @param array $fieldVal
     * @return bool
     */
    public static function updateField($condition, $fieldVal = [])
    {

        $model = new self();
        foreach ($fieldVal as $k => $v) {

            if (!$model->hasAttribute($k)) {
                unset($fieldVal[$k]);
                continue;
            }

            // 数字字段转[JSON]
            if (is_array($v)) $fieldVal[$k] = json_encode($fieldVal, JSON_UNESCAPED_UNICODE);
        }

        $dbTran = \Yii::$app->db->beginTransaction();
        try {

            ### 取出已更新的数据库条目
            // 取出数据库条目
            $dbList = NewsCategoryDbModel::find()->where($condition)->indexBy('id')
                ->asArray()->all();
            // 数据库为空的直接为更新成功
            if (!$dbList) {
                $dbTran->rollBack();
                return true;
            }
            // 取出数据库编号
            $dbIdList = array_column($dbList, 'id');

            ### 取出已更新的缓存条目
            $cacheList = self::find()->where($condition)->asArray()->all();
            // 缓存条目为空的直接 默认空数组
            if (empty($cacheList)) $cacheList = [];
            // 取出缓存编号
            $cacheIdList = array_column($cacheList, 'id');

            ### 取出差集添加；取出交集更新
            // 数据库为主取出[差集]
            $diffIdList = array_diff($dbIdList, $cacheIdList);
            // 数据库为主取出[交集]
            $interIdList = array_intersect($dbIdList, $cacheIdList);

            ### 数据提交
            // 先将数据库更新
            $result = NewsCategoryDbModel::updateField($condition, $fieldVal);
            if (!$result) {
                $dbTran->rollBack();
                return false;
            }

            // 缓存 - 更新数据
            if (!empty($interIdList) && !self::updateAll($fieldVal, $condition)) {
                $dbTran->rollBack(); // 这里失败先回滚吧 管理员可以看到数据未变化
                return false;
            }

            // 添加数据
            foreach ($diffIdList as $k => $v) {
                if (empty($dbList[$v])) continue;
                $model = new self();
                /* @var array $dbList [$v] */
                $model->setAttributes(ArrayHelper::merge($dbList[$v], $fieldVal));
                if (!$model->insert(false)) {
                    $dbTran->rollBack(); // 这里失败先回滚吧 管理员可以看到数据未变化
                    return false;
                }
            }

            $dbTran->commit();
            // 否则成功
            return true;
        } catch (\Exception $error) {

            $dbTran->rollBack();

            // 记录下错误日志
            \Yii::error([

                "``````````````````````````````````````````````````````````",
                "``                       缓存错误                          ``",
                "`` 错误详情: [[文章分类]缓存操作模型]缓存中修改[指定字段]失败       ``",
                "`` {$error->getMessage()}                                ``",
                "`` 错误信息和参数详情:                                      ``",
                "`````````````````````````````````````````````````````````",
                $error->getTraceAsString()
            ], 'cache');

            // 静态错误
            self::$error_['cache_error'] = empty(self::$error_['cache_error']) ?
                [$error->getMessage()] : array_merge(self::$error_['cache_error'], [$error->getMessage()]);

            return false;
        }
    }

    /**
     * 获取静态错误
     * @return mixed
     */
    public static function getStaticErrors()
    {
        return self::$error_;
    }
}
